package com.hhd.pdf.variable;

import com.hhd.pdf.CommonUtils;
import lombok.Builder;
import lombok.Getter;
import lombok.Setter;

import java.util.LinkedList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

public class CompositeFormula {
    Pattern pattern = Pattern.compile("\\{\\{[\\ \\d\\w\\.]+\\}\\}");
    private final String formulaText;
    List<Object> fragments = new LinkedList<>();

    public CompositeFormula(String formulaText) {
        this.formulaText = formulaText;
    }

    public static CompositeFormula formula(String formula) {
        CompositeFormula compositeFormula = new CompositeFormula(formula);
        compositeFormula.parse();
        return compositeFormula;
    }

    private void parse() {
        Matcher matcher = pattern.matcher(this.formulaText);
        LinkedList<MatchResult> results = getMatchResults(matcher);
        doFragments(results);
        parseFormulas();
    }

    private void parseFormulas() {
        fragments = fragments.stream().map(o -> {
            if (o instanceof String) return o;
            MatchResult result = (MatchResult) o;
            return parseFormula(result);
        }).collect(Collectors.toList());
    }

    private Object parseFormula(MatchResult result) {
        String string = result.getString();
        string = string.replaceAll("\\{\\{", "");
        string = string.replaceAll("}}", "");
        return ParsedFormula.formula(string);
    }

    private static LinkedList<MatchResult> getMatchResults(Matcher matcher) {
        LinkedList<MatchResult> results = new LinkedList<>();
        while (matcher.find()) {
            results.add(MatchResult.builder()
                    .start(matcher.start())
                    .end(matcher.end())
                    .string(matcher.group())
                    .build());
        }
        return results;
    }

    private void doFragments(LinkedList<MatchResult> results) {
        int end = 0;
        for (MatchResult result : results) {
            String before = this.formulaText.substring(end, result.getStart());
            if (!CommonUtils.isEmpty(before)) {
                fragments.add(before);
            }
            fragments.add(result);
            end = result.getStart() + result.getString().length();
        }
        if (end < this.formulaText.length()) {
            fragments.add(this.formulaText.substring(end, this.formulaText.length()));
        }
    }

    public List<ParsedFormula> getFormulas() {
        return fragments.stream()
                .filter(o -> o instanceof ParsedFormula)
                .map(o -> (ParsedFormula) o)
                .collect(Collectors.toList());
    }

    public String joinFormulas() {
        List<String> values = fragments.stream().map(o -> {
            if (o instanceof String) return (String) o;
            ParsedFormula formula = (ParsedFormula) o;
            return formula.getFormulaValue();
        }).collect(Collectors.toList());

        StringBuilder stringBuilder = new StringBuilder();
        values.stream().forEach(stringBuilder::append);
        return stringBuilder.toString();
    }

    public void execute(VariableContext variableContext) {
        for (Object fragment : this.fragments) {
            if (fragment instanceof ParsedFormula) {
                ParsedFormula formula = (ParsedFormula) fragment;
                variableContext.execute(formula);
            }
        }
    }

    @Getter
    @Setter
    @Builder
    static class MatchResult {
        private int start;
        private int end;
        private String string;

        @Override
        public String toString() {
            return string;
        }
    }
}
